# -*- coding:utf-8 -*-

""" 
市场匹配脚本
1. 通配不同平台市场名称
2. 写入对标市场的配置信息
3. 定期的更新配置信息
4. 自动建立市场订阅
5. 自动取消市场下架市场订阅
"""

import random
import json
import re
import time
import requests
import os
import sys
import numpy as np
from collections import Counter
from concurrent.futures import ThreadPoolExecutor, wait, ALL_COMPLETED

sys.path.append(os.path.split(os.path.split(__file__)[0])[0])  # NOQA
sys.path.append(os.path.dirname(os.path.dirname(__file__)))  # NOQA
sys.path.append(os.path.dirname(os.getcwd()))  # NOQA
from utils.myredis import RedisConnect  # NOQA
from api.constant import *
from api.log import logger


thread_pool = ThreadPoolExecutor(max_workers=3)


class Plat(RedisConnect):
    """
    v0.0.0
    redis 选择 1 号库
    缺少一个过滤 下掉的市场
    ----
    v0.0.1
    增加市场下架功能
    """

    name = PLAT_LATEST_SPOT_NAME
    key = PLAT_LATEST_SPOT_FORMAT_KEY
    ex_key = PLAT_LATEST_SPOT_FORMAT_EXCHANGE_KEY
    config_key = PLAT_LATEST_SPOT_FORMAT_CONFIG_KEY
    tickers_key = PLAT_LATEST_SPOT_FORMAT_TICKER_KEY

    def __init__(self):
        """ """

        super().__init__()
        self.xt_markets = map(lambda x: x.decode() if isinstance(
            x, bytes) else x,  self.get_xt_data())

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return
        markets = [key for key in data]
        return markets

    def request(self, method, url):
        """ """

        count = 3
        time.sleep(5)
        res = None

        while count > 0:
            try:
                res = requests.request(method, url, timeout=3)
                res.raise_for_status()
            except Exception as e:
                msg = " [get market list error .....] {}".format(e)
                logger.info(msg)
                count -= 1
                time.sleep(random.uniform(0, count + 1))
            else:
                return res

    def _filter_market(self, target_data, target_func, depth_handle=None, drop_market=None):
        """ """

        union_markets = []
        exchange_markets = dict()

        for market in self.xt_markets:

            target_market = target_func(market)
            if target_market in target_data:

                union_markets.append(market)                # union
                if not depth_handle:
                    exchange_markets.setdefault(market, target_market)
                else:
                    target_market = depth_handle(target_market)
                    exchange_markets.setdefault(market, target_market)

        return union_markets, exchange_markets

    def filter_market(self, data):
        """
        target XT
        --------
        filter makret diff
        """

        pass

    def persist(self, key, data, flag=""):
        """
        redis:
        --------
        key ----> plat:markets
        value ---> json
        --------
        更新市场同时
         1. 旧市场取消订阅
         2. 旧市场添加到黑名单
         3. 新市场需要重新划分,划分的条件根据key的数量来匹配后的均值

        ::param del_data        需要取消订阅的市场
        ::param update_data     需要新增的订阅市场
        """

        if not self.zcard(key):

            if not data:

                return None, None
            self.zadd(key, data)

        # del
        old_data = self.zrange(key)
        if set(old_data) == set(data):

            return None, None

        del_data = set(old_data).difference(set(data))
        self.zrem(key, del_data)

        # update
        update_data = set(data).difference(set(old_data))
        self.zadd(key, update_data)

        return del_data, update_data

    def ex_persist(self, data: dict):
        """ """

        for k, v in data.items():
            k = k if isinstance(k, str) else k.decode()
            key = self.ex_key.format(self.name, k)
            self.set(key, v)

    def ticker_persist(self, key, data: dict):
        """ """

        self.set(key, str(data))

    def get_data(self):
        """ """

        pass

    def parse_data(self, res):
        """ """

        if not res:

            return res

        data = res.json()

        return data

    def get_xt_data(self):
        """ """

        return self.zrange(Plat.key)

    def ex_cf_persist(self, data: dict, match_data=None):
        """ exchange config of markets """

        for k, v in data.items():
            if not isinstance(v, str):
                v = json.dumps(v)

            if match_data:
                if k not in match_data:
                    continue

            key = self.config_key.format(self.name, k)
            self.set(key, v)

    def get_market_zset(self, key):
        """ """

        old_data = map(lambda x: x.decode() if isinstance(
            x, bytes) else x, self.zrange(key))

        return old_data

    def get_config_math(self, data):
        """ """

        return data

    def get_precision_num(self, precision) -> int:
        """ """

        if not isinstance(precision, str):
            precision = '{}'.format(precision)

        return Counter(precision)['0']

    def get_precision(self, num) -> float:
        """ """

        return 1 * 10 ** (-num)

    def sub_market(self, data, key, bkey=None):
        """
        程序启动时候执行一次,市场变更时候执行
        订阅之前需要检测市场是否在黑名单中,如果存在则删除

        ::param :key 需要订阅的key
        ::param :data 需要订阅的市场主题
        """
        self.zadd(key, data)

    def del_market(self, key, data):
        """
        市场变更时候来改变市场的订阅的状态
        ::param : key 需要取消订阅的key
        ::param : data 需要取消订阅的市场主题
        """

        self.sadd(key, data)

    def __repr__(self):
        """ """

        return "<{.name}>".format(self)


class Xt(Plat):
    """ """

    name = XT_LATEST_SPOT_NAME
    url = XT_LATEST_SPOT_URI
    # url = "http://api.fixerror.fun:81/data/api/v1/getMarketConfig"
    # url_config = "http://api.fixerror.fun:81/data/api/v1/getMarketConfig"
    key = XT_LATEST_SPOT_FORMAT_KEY
    blacklist_key = XT_LATEST_SPOT_BALCKLIST_KEY
    subscribe_key_prefix = XT_LATEST_SPOT_SUBSCRIBE_PREIFX_KEY
    switch_times = SWITCH_TIMES

    def match_market(sym): return sym

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        try:
            data = [x["symbol"] for x in data["result"]["symbols"] if x["state"] == "ONLINE"]
        except:
            msg = "[plat_format] 数据获取失败 >>> {} {}".format(res, res.url)
            logger.info(msg)
            return

        return data

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url)

    def config_format(self, res):
        """ """

        return self.parse_data(res)

    def get_tickers(self):
        """ """

        return super().request('GET', XT_LATEST_SPOT_TICKER_URI)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, Xt.match_market)


class XTv4Spot(Plat):
    """ """

    name = XTV4SPOT_LATEST_SPOT_NAME
    url = XTV4SPOT_LATEST_SPOT_URI
    # url = "http://api.fixerror.fun:81/data/api/v1/getMarketConfig"
    # url_config = "http://api.fixerror.fun:81/data/api/v1/getMarketConfig"
    key = XTV4SPOT_LATEST_SPOT_FORMAT_KEY
    blacklist_key = XTV4SPOT_LATEST_SPOT_BALCKLIST_KEY
    subscribe_key_prefix = XTV4SPOT_LATEST_SPOT_SUBSCRIBE_PREIFX_KEY
    switch_times = SWITCH_TIMES

    def match_market(sym): return sym

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        try:
            data = [x["symbol"] for x in data["result"]["symbols"] if x["state"] == "ONLINE"]
        except:
            msg = "[plat_format] 数据获取失败 >>> {} {}".format(res, res.url)
            logger.info(msg)
            return

        return data

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)
        try:
            market_info = {
                info["symbol"]: info for info in data["result"]["symbols"]
            }
        except Exception as e:
            return

        return market_info

    def get_tickers(self):
        """ """

        return super().request('GET', XTV4SPOT_LATEST_SPOT_TICKER_URI)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, Xt.match_market)


class HuoBi(Plat):
    """ """

    name = HUOBI_LATEST_SPOT_NAME
    url = HUOBI_LATEST_SPOT_URI
    url_config = HUOBI_LATEST_SPOT_RETAIN_URI
    key = HUOBI_LATEST_SPOT_FORMAT_KEY
    switch_times = SWITCH_TIMES

    def match_market(sym): return sym.replace("_", "")

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        symbol_infos = data["data"]
        data = [x["symbol"] for x in symbol_infos]

        return data

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, HuoBi.match_market)

    def get_data(self):
        """ """

        return super().request("GET", self.url_config)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def get_config_math(self, data):
        """ """

        return map(HuoBi.match_market, data)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)
        markets_info = {sym['symbol']: {
            "minAmount": sym['sell-market-min-order-amt'],
            "minMoney": sym['min-order-value'],
            "pricePoint": sym['price-precision'],
            "coinPoint": sym['amount-precision'],
            "maker": 0,
            "taker": 0,
        } for sym in data['data']}

        return markets_info


class Binance(Plat):
    """ """

    name = BINANCE_LATEST_SPOT_NAME
    url = BINANCE_LATEST_SPOT_URI
    # url = "https://www.binancezh.co/api/v3/exchangeInfo"
    url_config = BINANCE_LATEST_SPOT_RETAIN_URI
    key = BINANCE_LATEST_SPOT_FORMAT_KEY
    blacklist_key = BINANCE_LATEST_SPOT_BALCKLIST_KEY
    subscribe_key_prefix = BINANCE_LATEST_SPOT_SUBSCRIBE_PREIFX_KEY
    switch_times = SWITCH_TIMES

    special_market = {"bchabc": "bch", "gitcoin": "gtc"}

    def match_market(self, sym):
        """ 处理特殊市场 """

        sym = sym.decode() if not isinstance(sym, str) else sym
        for markt, value in self.special_market.items():
            if sym.startswith(markt):

                return sym.replace(markt, value).upper()

        return sym.replace("_", "").upper()

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        try:
            symbol_infos = data["symbols"]
            value = [x["symbol"] for x in symbol_infos]
        except Exception as e:
            value = None
            msg = "[plat_format] 获取数据失败>>>  {} {}".format(data, res.url)
            logger.info(msg)

        return value

    def get_data(self):
        """ """

        return super().request("GET", self.url_config)

    def filter_market(self, data):
        """ """

        return self._filter_market(data, self.match_market)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def get_config_math(self, data):
        """ """

        return map(self.match_market, data)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)
        try:
            markets_info = {sym['symbol']: {
                "minAmount": sym['filters'][2]['minQty'],
                "minMoney": sym['filters'][0]['minPrice'],
                "pricePoint": self.get_precision_num(sym['filters'][0]['minPrice']),
                "coinPoint": self.get_precision_num(sym['filters'][0]['minPrice']),
                "maker": 0,
                "taker": 0,
            } for sym in data['symbols']}
        except Exception as e:
            msg = "[config_format] >>> 数据获取失败 {} {}".format(data, res.url)
            logger.info(msg)

        return markets_info

    def get_tickers(self):
        """ """

        return super().request('GET', BINANCE_LATEST_SPOT_TICKER_URI)


class Zb(Plat):
    """ """

    name = ZB_LATEST_SPOT_NAME
    # https://api.zb.team/data/v1/markets
    # url = "http://api.zb.live/data/v1/markets"
    url = ZB_LATEST_SPOT_URI
    url_config = ZB_LATEST_SPOT_RETAIN_URI
    key = ZB_LATEST_SPOT_FORMAT_KEY
    switch_times = SWITCH_TIMES

    def match_market(x): return x
    def depth_handle(x): return x.replace("_", "")

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        symbol_infos = [x for x in data]

        return symbol_infos

    def get_data(self):
        """ """

        return super().request("GET", self.url_config)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, Zb.match_market, Zb.depth_handle)

    def get_config_math(self, data):
        """ """

        return data

    def config_format(self, res):
        """ """

        data = self.parse_data(res)

        markets_info = {sym: {
            "minAmount": value['minAmount'],
            "minMoney": value['minSize'],
            "pricePoint": value['priceScale'],
            "coinPoint": value['amountScale'],
            "maker": 0,
            "taker": 0,
        } for sym, value in data.items()}

        return markets_info


class Gate(Plat):
    """ """

    name = GATE_LATEST_SPOT_NAME  # 1
    url = GATE_LATEST_SPOT_URI
    url_config = GATE_LATEST_SPOT_RETAIN_URI
    key = GATE_LATEST_SPOT_FORMAT_KEY
    switch_times = SWITCH_TIMES

    special_market = {}  # 处理市场兼容 {"xt-market":"exchange-market"}

    def match_market(self):
        """ 处理特殊市场的业务逻辑 """

        def match_market(x): return x
        def depth_handle(x): return x.upper()

        return match_market, depth_handle

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        symbol_infos = [x['pair'] for x in data['data']]

        return symbol_infos

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, self.match_market)

    def get_config_math(self, data):
        """ """

        return data

    def config_format(self, res):
        """ """

        data = self.parse_data(res)

        markets_info = {list(sym.keys())[0]: {
            "minAmount": list(sym.values())[0]['min_amount'],
            "minMoney": list(sym.values())[0]['min_amount_b'],
            "pricePoint": list(sym.values())[0]['decimal_places'],
            "coinPoint": list(sym.values())[0]['amount_decimal_places'],
            "maker": list(sym.values())[0]['fee'],
            "taker": list(sym.values())[0]['fee'],
        } for sym in data['pairs']}

        return markets_info


class Okex(Plat):
    """ """

    name = OKEX_LATEST_SPOT_NAME  # 1
    url = OKEX_LATEST_SPOT_URI
    url_config = OKEX_LATEST_SPOT_RETAIN_URI
    key = OKEX_LATEST_SPOT_FORMAT_KEY
    switch_times = SWITCH_TIMES

    def match_market(x): return x.replace("_", "-").upper()

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        symbol_infos = [x['instId'] for x in data['data']]

        return symbol_infos

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, Okex.match_market)

    def get_config_math(self, data):
        """ """

        return map(Okex.match_market, data)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)

        markets_info = {sym['instId']: {
            "minAmount": sym['minSz'],
            "minMoney": sym['tickSz'],
            "pricePoint": self.get_precision_num(sym['tickSz']),  # 精度处理
            "coinPoint": sym['minSz'],
            "maker": sym['category'],
            "taker": sym['category'],
        } for sym in data['data']}

        return markets_info


class Bitfinex(Plat):
    """ """

    name = BITFINEX_LATEST_SPOT_NAME
    url = BITFINEX_LATEST_SPOT_URI
    key = BITFINEX_LATEST_SPOT_FORMAT_KEY
    switch_times = SWITCH_TIMES

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        return data

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        def match_market(x): return (x.split("_")[
            0][0:3] + x.split("_")[1].replace("usdt", "ust")) if x.endswith("usdt") else x

        def depth_handle(x): return ("t"+x.upper()) if x.endswith("ust") else x

        return super()._filter_market(data, match_market, depth_handle)


class MXC(Plat):
    """ """

    name = MXC_LATEST_SPOT_NAME
    url = MXC_LATEST_SPOT_URI
    url_config = MXC_LATEST_SPOT_RETAIN_URI
    switch_times = SWITCH_TIMES
    key = "{}:markets".format(name)

    def match_market(x): return x.upper()

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        markets = [market["symbol"] for market in data["data"]]
        return markets

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        return super()._filter_market(data, MXC.match_market)

    def get_config_math(self, data):
        """ """

        return map(MXC.match_market, data)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)

        markets_info = {sym['symbol']: {
            "minAmount": self.get_precision(sym['quantity_scale']),
            "minMoney": sym['min_amount'],   # 精度处理
            "pricePoint": sym['price_scale'],
            "coinPoint": sym['quantity_scale'],
            "maker": sym['maker_fee_rate'],
            "taker": sym['taker_fee_rate'],
        } for sym in data['data']}

        return markets_info


class FTX(Plat):
    """ """

    name = FTX_LATEST_SPOT_NAME
    url = FTX_LATEST_SPOT_URI
    url_config = FTX_LATEST_SPOT_RETAIN_URI
    key = FTX_LATEST_SPOT_FORMAT_KEY
    blacklist_key = FTX_LATEST_SPOT_BALCKLIST_KEY
    subscribe_key_prefix = FTX_LATEST_SPOT_SUBSCRIBE_PREIFX_KEY
    switch_times = SWITCH_TIMES

    def match_market(x): return x.replace("_", "/").upper()

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        return [info["name"] for info in data["result"]]

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        return self._filter_market(data, FTX.match_market)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url_config)

    def get_config_math(self, data):
        """ """

        return map(FTX.match_market, data)

    def config_format(self, res):

        data = self.parse_data(res)
        markets_info = {sym['name']: {
            "minAmount": sym['sizeIncrement'],
            "minMoney": sym['priceIncrement'],
            "pricePoint": self.get_precision_num(sym['priceIncrement']),
            "coinPoint": self.get_precision_num(sym['sizeIncrement']),
            "maker": 0,
            "taker": 0,
        } for sym in data['result']}

        return markets_info

    def get_tickers(self):
        """ """

        return super().request('GET', FTX_LATEST_SPOT_TICKER_URI)


class XTFutureU(Plat):
    """um | cm"""

    name = XT_V3_FUTURE_NAME
    url = XT_V3_FUTURE_URI
    url_config = XT_V3_FUTURE_RETAIN_URI
    blacklist_key = XT_V3_FUTURE_BALCKLIST_KEY
    subscribe_key_prefix = XT_V3_FUTURE_SUBSCRIBE_PREIFX_KEY
    key = XT_V3_FUTURE_FORMAT_KEY
    switch_times = SWITCH_TIMES

    def match_market(x): return x

    def plat_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            return

        return [info["symbol"] for info in data["result"]]

    def get_data(self):
        """ """

        return super().request("GET", self.url)

    def filter_market(self, data):
        """ """

        return self._filter_market(data, XTFutureU.match_market)

    def get_market_config(self):
        """ """

        return super().request("GET", self.url)

    def get_config_math(self, data):
        """ """

        return map(XTFutureU.match_market, data)

    def config_format(self, res):
        """ """

        data = self.parse_data(res)
        if not data:
            msg = "[config_format] 没有获取到数据 {} {}".forget(data, res.url)
            logger.info(msg)
            return
        markets_info = {sym['symbol']: sym for sym in data['result']}

        return markets_info

    def get_tickers(self):
        """ """

        return super().request('GET', XT_V3_FUTURE_TICKER_URI)


class DataBroker:
    """ """

    def __init__(self, obj):

        self.obj = obj
        pass

    def async_handle(self):
        """ 
        订阅市场获取以及数据处理 
        默认启动市场订阅
        获取原始市场信息如果存在的话
        """

        origin_markets = self.obj.zrange(self.obj.key)
        keys, num = self.acquire_match_keys(self.obj.subscribe_key_prefix)
        self.init_sub_market(keys, num, origin_markets)
        symbols, res = self.get_symbols()
        if not symbols:
            return

        ex_markets = self.update_sub_info(symbols, keys, num)
        self.handle_special_method(res, symbols, origin_markets)
        self.to_cache(ex_markets)

    def async_handle_config(self):
        """ 处理额外生成的数据 """

        # market config
        res = self.obj.get_market_config()
        data = self.obj.config_format(res)
        if not data:
            return

        # get markets convert to xt
        data_z = self.obj.get_market_zset(self.obj.key)
        data_z = self.obj.get_config_math(data_z)
        if not data_z:
            return
        # set market config
        self.obj.ex_cf_persist(data, list(data_z))

        logger.info("[+] {} 平台 | 市场通配执行完毕............".format(self.obj.name))

    def init_sub_market(self, keys, num, origin_markets):
        """ 
        启动只需要执行一次即可
        查看市场是否多个实例，如果多个实例则均分
        """

        if not self.obj.switch_times and keys:
            # 清理数据
            for key in keys:
                self.obj.delete(key)

            if all([keys, num]):
                msg = "[keys] keys信息>>>>  {} {}".format(keys, num)
                logger.info(msg)
                self._divide_market(keys, num, origin_markets, self.obj.blacklist_key,
                                    self.obj.sub_market)

            self.obj.switch_times = True

    def get_symbols(self):
        """ markets """

        res = self.obj.get_data()
        symbols = self.obj.plat_format(res)

        return symbols, res

    def update_sub_info(self, symbols, keys, num):
        """ 动态更新订阅和黑名单的状态 """
        # intersection(ms), pairs(ex_markets) of obj/xt

        ms, ex_markets = self.obj.filter_market(symbols)
        # update obj:market list
        del_data, update_data = self.obj.persist(self.obj.key, ms, flag="xt")

        if del_data:
            msg = "[del_data]  {} {}".format(del_data, self.obj.name)
            logger.info(msg)
            self.obj.del_market(self.obj.blacklist_key, del_data)

        if update_data:
            msg = "[update_data]  {} {}".format(update_data, self.obj.name)
            logger.info("[update_data] ", update_data, self.obj.name)
            # 取消下架市场
            # 新增新市场
            self._divide_market(keys, num, update_data,
                                self.obj.blacklist_key, self.obj.sub_market)

        return ex_markets

    def handle_special_method(self, res, symbols, origin_markets):
        """ 处理一些平台特殊情况 """

        if self.obj.name == "xtv3future":
            data = self.obj.config_format(res)
            if not data:
                return
            self.obj.ex_cf_persist(data)

        if self.obj.name == "xt":
            # tickers
            res = self.obj.get_tickers()
            n_data = self.obj.parse_data(res)
            self.obj.ticker_persist(self.obj.tickers_key, n_data["result"])
            if not origin_markets:
                self.obj.zadd(self.obj.key, symbols)

    def to_cache(self, ex_markets):
        """ 缓存起来 """

        self.obj.ex_persist(ex_markets)

    def acquire_match_keys(self, pattern):
        """ """

        keys = self.obj.smembers(EXCHANGE_SUB_KEYS)
        regex = re.compile(pattern)

        re_keys = []
        for key in keys:

            if not regex.match(key):
                continue
            re_keys.append(key)

        return re_keys, len(re_keys)

    def _get_sub_recover_content(self, key):
        """ """
        recover_content = self.obj.zrange(key)
        sub_key = key.replace("recover", "sub")
        sub_content = self.obj.smembers(sub_key)
        already_market = recover_content + sub_content

        return already_market

    def _filter_already_market(self, markets, keys):
        """ """
        already_market = []
        for key in keys:
            already_market += self._get_sub_recover_content(key)

        result = set(markets) - set(already_market)

        return list(result)

    def _merge_market_content(self, keys):
        """ """

        sub_key_num_slice = np.array(
            [self.obj.scard(k.replace("recover", "sub")) for k in keys])
        key_value_num_slice = np.array([self.obj.zcard(k) for k in keys])
        num_slice_array = sub_key_num_slice + key_value_num_slice

        return num_slice_array.tolist()

    def _get_list_min_index(self, data):
        """ """
        num_slice_min = min(data)
        min_index = data.index(num_slice_min)

        return min_index

    def _get_sub_recover_content(self, key):
        """ """
        recover_content = self.obj.zrange(key)
        sub_key = key.replace("recover", "sub")
        sub_content = self.obj.smembers(sub_key)
        already_market = recover_content + sub_content

        return already_market

    def _step_already_market(self, keys, symbols, bkey):
        """ """

        markets = []
        for symbol in symbols:
            flag = False

            for key in keys:

                already_market = self._get_sub_recover_content(key)
                # 判断是否在黑名单如果存在则删除 | 启动第一次
                if not self.obj.switch_times and self.obj.sismember(bkey, symbol):
                    self.obj.srem(bkey, symbol)

                if already_market and symbol in already_market:
                    flag = False
                    continue
                flag = True

            if flag:
                markets.append(symbol)

        return markets

    def _divide_market(self, keys, num, markets, bkey, handle):
        """ """
        if not markets or not keys:
            return
        all_count = len(markets)

        need_sub_markets = self._filter_already_market(markets, keys)
        count = int(all_count / num if num != 0 else 1)

        # 写入到recover中来做默认市场订阅的市场
        for i in range(num + 1):

            # 优先选择存在最小的key的数量作为补偿
            num_slice = self._merge_market_content(keys)
            min_index = self._get_list_min_index(num_slice)

            try:
                # 优先选择数量最小的key
                # 前提判断这些值是否已经存在
                sub_markets = self._step_already_market(
                    keys, need_sub_markets[count * i: (i + 1) * count], bkey)
            except Exception:
                # 处理奇数 , keys索引越界处理
                if count * i < all_count:
                    # 前提判断这些值是否已经存在
                    sub_markets = self._step_already_market(
                        keys, need_sub_markets[count * i: (i + 1) * count], bkey)

                    msg = "[divide_market] 边界处理额外市场 >>> {} {}".format(keys[min_index], sub_markets)
                    logger.info(msg)

            handle(sub_markets, keys[min_index], bkey)


# 配置执行自动匹配的交易所市场
EXCHANGE_CONFIG = [Xt, Binance, XTv4Spot, XTFutureU, FTX]
# EXCHANGE_CONFIG = [Binance]


class Main:

    def start(self):
        """ """
        erros = []
        t0 = time.perf_counter()

        for obj in EXCHANGE_CONFIG:
            broker = DataBroker(obj())

            try:
                broker.async_handle()
                broker.async_handle_config()

            except AttributeError as e:
                erros.append(obj)
                logger.info(f"< get markets error > ..... {e}, {obj.name}")

        logger.info(
            "[+] 程序执行完 花费时间{}....................................".format(time.perf_counter() - t0))

        if erros:
            msg = "[-] 异常的平台 {}".format(erros)
            logger.info(msg)


def get_markets():
    """
    crontab
    """
    main = Main()
    main.start()


# pm2 start main.py  --name marketMatch --interpreter python3
# del binance:markets:recover:1
# del binance:markets:recover:0
# del binance:markets:sub:0
# del binance:markets:sub:1

# smembers ws:exchange:sub:keys
# smembers binance:markets:blacklist
# zrange binance:markets:recover:1 0 -1
# zrange xtv3future:markets:recover:0 0 -1
# smembers binance:markets:sub:0
# smembers binance:markets:sub:1

# sadd xtv3future:markets:blacklist btc_usdt
# srem xtv3future:markets:blacklist btc_usdt
if __name__ == "__main__":
    get_markets()
